using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Utilities;

namespace SimpleServiceBus.Bus.Subscriptions
{
    /// <summary>
    /// Manages subscriptions for published messages.
    /// </summary>
    /// <remarks>
    /// Thread safe.
    /// </remarks>
    public class DefaultSubscriptionManager : EndpointServiceBase, ISubscriptionManager
    {
        private readonly ReaderWriterLock _rwLock = new ReaderWriterLock();
        private readonly Dictionary<Type, MessageTypeSubscription> _subscriptions;

        public DefaultSubscriptionManager()
        {
            _subscriptions = new Dictionary<Type, MessageTypeSubscription>();
        }

        public DefaultSubscriptionManager(IMessageBus bus,ISubscriptionRepository repository):this()
        {
            MessageBus = bus;
            Repository = repository;
        }

        public ISubscriptionRepository Repository { get; private set; }
        public IMessageBus MessageBus { get; private set; }

        #region ISubscriptionManager Members

        public void Publish(object message)
        {
            IList<string> subscribers = GetSubscribersForMessage(message);
            if (subscribers != null && subscribers.Count > 0)
                MessageBus.Send(message, subscribers.ToArray());
        }

        public IList<string> GetSubscribersForMessage(object message)
        {
            var tempEnvelope = new MessageEnvelope {Body = message};
            return GetSubscribersForMessage(tempEnvelope);
        }

        public virtual void SubscribeToAll(string subscriberAddress)
        {
            SubscribeToMessage(typeof (object), subscriberAddress);
        }

        public virtual void SubscribeToMessage(Type messageType, string subscriberAddress)
        {
            using (new WriterLock(_rwLock, TimeSpan.FromSeconds(10)))
            {
                if (Repository != null)
                    Repository.AddSubscription(messageType, subscriberAddress);

                MessageTypeSubscription subscription;
                _subscriptions.TryGetValue(messageType, out subscription);

                if (subscription == null)
                    subscription = new MessageTypeSubscription(messageType);

                subscription.AddSubscribers(subscriberAddress);

                _subscriptions[messageType] = subscription;
            }
        }

        public virtual void UnsubscribeFromAll(string subscriberAddress)
        {
            using (new WriterLock(_rwLock, TimeSpan.FromSeconds(10)))
            {
                if (Repository != null)
                    Repository.RemoveAllSubscriptionsForSubscriber(subscriberAddress);

                foreach (MessageTypeSubscription subscription in _subscriptions.Values)
                {
                    subscription.RemoveSubscribers(subscriberAddress);
                }
            }
        }

        public virtual void UnsubscribeFromMessage(Type messageType, string subscriberAddress)
        {
            using (new WriterLock(_rwLock, TimeSpan.FromSeconds(10)))
            {
                if (Repository != null)
                    Repository.RemoveSubscription(messageType, subscriberAddress);

                MessageTypeSubscription subscription;
                _subscriptions.TryGetValue(messageType, out subscription);

                if (subscription != null)
                {
                    subscription.RemoveSubscribers(subscriberAddress);
                }
            }
        }

        #endregion

        protected virtual IList<string> GetSubscribersForMessage(MessageEnvelope messageEnvelope)
        {
            var subscribers = new List<string>();

            using (new ReaderLock(_rwLock, TimeSpan.FromSeconds(10)))
            {
                foreach (ISubscription sub in _subscriptions.Values)
                {
                    if (sub.Matches(messageEnvelope) && sub.Subscribers != null)
                        subscribers.AddRange(sub.Subscribers);
                }
            }

            return subscribers;
        }


        protected override void StartService()
        {
            if (Repository == null)
                Repository = Endpoint.LocalServices.GetService<ISubscriptionRepository>();
            
            if (MessageBus == null)
                MessageBus = Endpoint.MessageBus;

            if (Repository != null)
            {
                IList<StoredSubscription> subs = Repository.GetAllSubscriptions();
                foreach (StoredSubscription sub in subs)
                {
                    SubscribeToMessage(sub.MessageType, sub.Subscriber);
                }
            }
        }

        protected override void ShutDownService()
        {
        }
    }
}